/*!  \file  UnitTester.h
 *	  \brief  
 * 
 *   \details   details
 *   \author    Yordan Madzhunkov German Research School for Simulation Sciences GmbH
 *   \version   1.0
 *   \date      01/17/2013 10:47:33 AM
 *   \copyright GNU Public License.
 */
#pragma once
#include "macros.h"
class UnitTester;
#include "CompileTime.h"
class Test : public CompileTime {
public:
	Test();
	virtual ~Test();
	const char * getName       () const { return name       ; };
	const char * getDescription() const { return description; };
	const char * getAuthor     () const { return author     ; };
	const char * getDeveloper  () const { return developer  ; };
	time_t       whenPassed    () const { return passed ? passedTime : 0; };
	bool         isPassed      () const { return passed; };
	virtual bool isMPI         () const { return false; };
	void setName 				(const char * val) { set(name       , val);}
	void setDescription  	(const char * val) { set(description, val);}
	void setTestAuthor		(const char * val) { set(author     , val);}
	void setDeveloperAuthor	(const char * val) { set(developer  , val);}
	static void set(char * & setting, const char * val);
	static bool fexists(const char * filename);
	friend class UnitTester;	
protected:
	void setPassedTime(const time_t pt) { passedTime = pt; passed = true;}; 
private:
	DISALLOW_COPY_AND_ASSIGN(Test);
	char *  name;
	char *  description;
	char *  author; // Quality Assurance, the person who wrote the test
	char *  developer;
	time_t  passedTime; // when the test was passed
	bool    passed; // true if test was passsed succesfully
};
 
class UnitTest : public Test {
public:
	UnitTest();
	virtual ~UnitTest();
	virtual bool test() const = 0;
private:
	DISALLOW_COPY_AND_ASSIGN(UnitTest);
};


template <typename T>
class Metric {
public:
	Metric (int maxMeasurements = 128, const char * units_ = NULL);
	virtual ~Metric();
	void addMeasurement(T val);
	void makeFinal(); // after making final, we can't add more samples
	void reset(); 		// start with new set of measurements
	T      getMeasurement(const int i) const { return sample[i]; };
	int    getSampleSize (           ) const { return size; };

/* The five-number summary is a descriptive statistic that provides
 * information about a set of observations. It consists of the five most
 * important sample percentiles:
 *
 * the sample minimum (smallest observation)
 * the lower quartile or first quartile
 * the median (middle value)
 * the upper quartile or third quartile
 * the sample maximum (largest observation) 
 * */
	
	T      getMin          () const { return min;    };
	T      getMax          () const { return max;    };
	double getAverage      () const { return avg;    };
	double getVariation    () const { return var;    };
	double getSigma        () const { return sigma;  };
	double getFirstQuater  () const { return first ; };
	double getMedian       () const { return median; };
	double getSecondQuater () const { return median; };
	double getThirdQuater  () const { return third ; };
	const char * getUnits  () const { return units ; };
	bool 	 hasUnits		  () const { return units != NULL; };	

	void   storeMetrics (double * RS results) const;


private:
	double getQuantile(const double percentage, T * sorted);
	DISALLOW_COPY_AND_ASSIGN(Metric);
	void sortSample() const;
	T *      sample;
	char *   units;
	double   avg, first, median, third, var, sigma;
	T 			min, max;
	int      size;

//	T min, max, avg, median, firstQuater, thirdQuater;
};
#include "Metric_impl.h"
template <typename T>
std::ostream& operator <<(std::ostream& os, const Metric<T> & m);

// Benchmark is piece of code that will execute given algorithm 
// on many test cases. For each test case sever metrics will be reported
//
class Map;
class Timer;
class Implementation;
class Benchmark : public Test {
public:
	Benchmark(Implementation & impl_, const int maxIntMetrics = 16, 
		const int maxFloatMetrics = 16 ); 
	virtual ~Benchmark();

	virtual void setRunConfiguration(const int n) = 0;
	virtual int  getProblemSize(const int configuration) const = 0;
	virtual void prepareRun() = 0;
	virtual void makeRun() = 0;
	virtual void finalizeRun() = 0;
	virtual void run();
	//	virtual void measureMetrics() = 0;
	int getNMetrics() const { return nIntMetrics + nFloatMetrics; };	
	inline void startTimings();
	inline void  stopTimings();
	bool allDatFilesArePresent() const;
	int    getNIntMetrics() const { return    nIntMetrics; };
	int  getNFloatMetrics() const { return  nFloatMetrics; };
	int getNMaxIntMetrics() const { return nMaxIntMetrics; };
	int    getNMaxMetrics() const { return nMaxIntMetrics + nFloatMetrics; };
	const char * getMetricName(int index) const;
	const char * getBaseName() const { return baseName; };
	char * getMetricFileName(const char * metricName, const char * directory,
		const char * extension = ".dat", const char * benchName = NULL) const; 
	Benchmark * getNext() const { return next; };
	void setNext (Benchmark * next_) { assert(next == NULL); next = next_; };	
private:
	DISALLOW_COPY_AND_ASSIGN(Benchmark);
protected:
	enum { nTimers = 6 };
	void addTimerMetrics();
	void addMemoryMetrics();
	void addSuccessMetrics();
	template <typename T>      
	void addMetric(Metric<T>  & metric, const char * name);
	template <typename T>      
	void getMetric(Metric<T>* & metric, const char * name);
	template <typename T>
	void reportResults(const Metric<T> & metric, const char * MetricName,
	const double * res);
	Implementation   & impl;
	Metric<int    > ** intMetrics;
	Metric<double > ** floatMetrics;
	Metric<int >     * memory;
	Metric<int >     * success;
	Map              * names;
	Timer            * timer;
	double             timings[nTimers];
	Metric<double >  * timeMetrics[nTimers];
	char 				  * baseName;
	Benchmark        * next;
	const char       * xlabel;         
	int nIntMetrics, nFloatMetrics, nMaxIntMetrics, nConfigurations, nRuns;
	bool setConfigurationEachRun;
};

class UnitTester {
public:
	enum { MaxNumberOfTests      = 128};
	enum { MaxNumberOfBenchmarks = 128};

	void registerTest   (UnitTest & test);
	void deregisterTest (UnitTest & test);

	void registerBench  (Benchmark & bench);
	void deregisterBench(Benchmark & bench);

	void runTests(); 				// runs all tests
	void runRecomiledTests(); 	// runs all tests that were recompiled

	void runTest(const unsigned int i);

	void runBenchmarks();
	void runBenchmark(const unsigned int i);

	int  getSVNRevision() { return myRev;};
	int  getSVNRevisionFromSVN();

	static UnitTester * instance();
	static UnitTester * instance(int argc, char *argv[]);

	const char * getBenchmarkDirectory() {
		return "./benchmarks/";
	}
	static bool isGoodInFileName(const char a)  {
		return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') ||
				 (a >= '0' && a <= '9') || a == '_' || a == '-' || a == '.';
	}
	const char * getLogFileName   () const { return "UnitTester.log"; };
	const char * getDataFileName  () const ;
	const char * getPassedStr     () const { return "Passed   "; };
	const char * getFailedStr     () const { return "Failed   "; };
	const char * getCompiledStr   () const { return "Compiled "; };
	void writeDataFile() const;
	bool readDataFile() const;
	
	virtual ~UnitTester();
	void setVerbose(const unsigned int newVerbose) { verbose = newVerbose;};
	
private:
	bool  writeToDataFile( Test & test, std::ofstream & fout) const;
	bool readFromDataFile( Test & test, std::ifstream & fin ) const;
	template <class T>
	bool readFromDataFile(T ** t, const unsigned int nT, std::ifstream & fin) const;
	template <class T>
	bool writeToDataFile (T ** t, const unsigned int nT, std::ofstream & fout) const;
	UnitTester(int argc = 0, char *argv[] = 0);
	static UnitTester * myInstance;
	UnitTest  **  tests;
	Benchmark **  benchs;
	char ** argv;
	Timer            * timer;
	unsigned int verbose;
	unsigned int nTests;
	unsigned int nBenchmarks;
	int myRev;
	int argc ; 
	bool 	allTests, allBenchmarks;
};

#define UNITTEST(x) class x : public UnitTest { \
public: \
	x (); \
	virtual ~x (){}; \
	virtual bool test() const; \
};

#define UNITTEST2(x, y, z) class x : public UnitTest { \
public: \
	x ( y &); \
	virtual ~x (){}; \
	virtual bool test() const; \
private:\
	DISALLOW_COPY_AND_ASSIGN(x);\
	y & z;\
};
